package javango.forms;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

import javango.forms.fields.CharField;
import javango.forms.fields.DateField;
import javango.forms.fields.Field;
import javango.forms.fields.FieldFactory;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class ModelForm extends BaseForm {
	
	private final static Log log = LogFactory.getLog(ModelForm.class);
	protected Class model;
	protected List<String> exclude;
	protected List<String> include;

	public ModelForm(FieldFactory fieldFactory) {
		super(fieldFactory);
	}

	@Override
	public ModelForm setInitial(Object bean) {
		// TODO do not bind to the object's class.. allowing for model forms that are nout bound to a class,  ie they only need modelfields
		// the logic behind this is modelchoice fields require they be in a model form,  but it may not always be desired to bind to a modelclass.
		if (this.model != null) {
			throw new RuntimeException("This form has already been bound to a class");
		}

		this.setModel(bean.getClass());		

		init();
				
		super.setInitial(bean);
//		try {
//			Class clazz = getModel();
//			while (clazz != null) {
//				java.lang.reflect.Field[] classFields = clazz.getDeclaredFields();
//				for (int i=0; i<classFields.length; i++) {
//					java.lang.reflect.Field field = (java.lang.reflect.Field)classFields[i];
//					String property = field.getName();
//					Field f = this._fields.get(property);
//					if (f != null) {
//						f.setInitial(PropertyUtils.getProperty(bean, property));
//					}
//				}
//				clazz = clazz.getSuperclass();
//			}
//		} catch (Exception e) {
//			log.error(e,e);
//		}
//		
		return this;
	}

	protected void processField(java.lang.reflect.Field field) {
		try {
			if (log.isDebugEnabled()) log.debug("Processing Field :" + field);
			
			// first see if this modelform has overridden the field...
			field = this.getClass().getDeclaredField(field.getName());
			loadField(field);
		} catch (NoSuchFieldException e) {
			Class type = field.getType();
			Field baseField = null;

			baseField = fieldFactory.newFieldForClass(type);
			
			if (baseField != null) {
				baseField.setName(field.getName());
				Annotation[] annotations = field.getAnnotations();
				for (Annotation annotation: annotations) {
						baseField.handleAnnotation(annotation);
				}
				_fields.put(field.getName(), baseField);
			}
		}
	}
	
	protected void processModel() {
		Class clazz = getModel();
		if (include != null) {
			// only process the fields specified in include,  preserving order
			for(String fieldName : include) {
				try {
					processField(clazz.getDeclaredField(fieldName));
				} catch (NoSuchFieldException e) {
					log.error("Field not found :" + fieldName);
				}
			}
		} else while (clazz != null) {
			java.lang.reflect.Field[] classFields = clazz.getDeclaredFields();
			for (int i=0; i<classFields.length; i++) {
				java.lang.reflect.Field field = (java.lang.reflect.Field)classFields[i];
				String fieldName = field.getName();
				if (exclude != null && exclude.contains(fieldName)) {
					if (log.isDebugEnabled()) log.debug("Excluding Field :" + field);
				} else {
					processField(field);
				}
			}
			clazz = clazz.getSuperclass();
		}
	}
	
	@Override
	protected void init() {
		if(!init) {
			if (getModel() != null) {
				if (log.isDebugEnabled()) log.debug("Processing fields for " + getModel().toString());
				
				processModel();			
			}
		}
		
		// now process all fields in my class,  adding those that have not already been added.  This catches fields that are added via this ModelForm
		super.init();
	}
	
	public Class getModel() {
		return model;
	}
	public ModelForm setModel(Class modelClass) {
		this.model = modelClass;
		return this;
	}

	public String[] getExclude() {		
		return (String[])exclude.toArray();
	}

	public void setExclude(String... strings ) {
		exclude = Arrays.asList(strings);
	}

	public String[] getInclude() {
		return (String[])include.toArray();
	}

	public void setInclude(String... strings) {
		this.include = Arrays.asList(strings);
	}
}
